package graph

import (
	

	
	
	
	
	
	
	
	
	
	
)

// Option represents an option that can be used to configure a graph panel.
type Option func(graph *Graph) error

// DrawMode represents a type of visualization that will be drawn in the graph
// (lines, bars, points)
type DrawMode uint8

const (
	// Bars will display bars.
	Bars DrawMode = iota
	// Lines will display lines.
	Lines
	// Points will display points.
	Points
)

// NullValue describes how null values are displayed.
type NullValue string

const (
	// AsZero treats null values as zero values.
	AsZero NullValue = "null as zero"

	// AsNull treats null values as null.
	AsNull NullValue = "null"

	// Connected connects null values.
	Connected NullValue = "connected"
)

// LegendOption allows to configure a legend.
type LegendOption uint16

const (
	// Hide keeps the legend from being displayed.
	Hide LegendOption = iota
	// AsTable displays the legend as a table.
	AsTable
	// ToTheRight displays the legend on the right side of the graph.
	ToTheRight
	// Min displays the smallest value of the series.
	Min
	// Max displays the largest value of the series.
	Max
	// Avg displays the average of the series.
	Avg
	// Current displays the current value of the series.
	Current
	// Total displays the total value of the series.
	Total
	// NoNullSeries hides series with only null values from the legend.
	NoNullSeries
	// NoZeroSeries hides series with only 0 values from the legend.
	NoZeroSeries
)

// Graph represents a graph panel.
type Graph struct {
	Builder *sdk.Panel
	Alert   *alert.Alert
}

// New creates a new graph panel.
func ( string,  ...Option) (*Graph, error) {
	 := &Graph{Builder: sdk.NewGraph()}

	.Builder.AliasColors = make(map[string]interface{})
	.Builder.IsNew = false
	.Builder.GraphPanel.Tooltip.Sort = 2
	.Builder.GraphPanel.Tooltip.Shared = true

	for ,  := range append(defaults(), ...) {
		if  := ();  != nil {
			return nil, 
		}
	}

	return , nil
}

func defaults() []Option {
	return []Option{
		Draw(Lines),
		Span(6),
		Fill(1),
		Null(AsZero),
		LineWidth(1),
		Legend(NoZeroSeries, NoNullSeries),
		defaultAxes(),
	}
}

func defaultAxes() Option {
	return func( *Graph) error {
		.Builder.GraphPanel.YAxis = true
		.Builder.GraphPanel.XAxis = true
		.Builder.GraphPanel.Yaxes = []sdk.Axis{
			*axis.New().Builder,
			*axis.New(axis.Hide()).Builder,
		}
		.Builder.GraphPanel.Xaxis = *axis.New(axis.Unit("time")).Builder

		return nil
	}
}

// Links adds links to be displayed on this panel.
func ( ...links.Link) Option {
	return func( *Graph) error {
		.Builder.Links = make([]sdk.Link, 0, len())

		for ,  := range  {
			.Builder.Links = append(.Builder.Links, .Builder)
		}

		return nil
	}
}

// WithPrometheusTarget adds a prometheus query to the graph.
func ( string,  ...prometheus.Option) Option {
	 := prometheus.New(, ...)

	return func( *Graph) error {
		.Builder.AddTarget(&sdk.Target{
			RefID:          .Ref,
			Hide:           .Hidden,
			Expr:           .Expr,
			IntervalFactor: .IntervalFactor,
			Interval:       .Interval,
			Step:           .Step,
			LegendFormat:   .LegendFormat,
			Instant:        .Instant,
			Format:         .Format,
		})

		return nil
	}
}

// WithGraphiteTarget adds a Graphite target to the table.
func ( string,  ...graphite.Option) Option {
	 := graphite.New(, ...)

	return func( *Graph) error {
		.Builder.AddTarget(.Builder)

		return nil
	}
}

// WithInfluxDBTarget adds an InfluxDB target to the graph.
func ( string,  ...influxdb.Option) Option {
	 := influxdb.New(, ...)

	return func( *Graph) error {
		.Builder.AddTarget(.Builder)

		return nil
	}
}

// WithStackdriverTarget adds a stackdriver query to the graph.
func ( *stackdriver.Stackdriver) Option {
	return func( *Graph) error {
		.Builder.AddTarget(.Builder)

		return nil
	}
}

// DataSource sets the data source to be used by the graph.
func ( string) Option {
	return func( *Graph) error {
		.Builder.Datasource = &sdk.DatasourceRef{LegacyName: }

		return nil
	}
}

// Span sets the width of the panel, in grid units. Should be a positive
// number between 1 and 12. Example: 6.
func ( float32) Option {
	return func( *Graph) error {
		if  < 1 ||  > 12 {
			return fmt.Errorf("span must be between 1 and 12: %w", errors.ErrInvalidArgument)
		}

		.Builder.Span = 

		return nil
	}
}

// Height sets the height of the panel, in pixels. Example: "400px".
func ( string) Option {
	return func( *Graph) error {
		.Builder.Height = &

		return nil
	}
}

// Description annotates the current visualization with a human-readable description.
func ( string) Option {
	return func( *Graph) error {
		.Builder.Description = &

		return nil
	}
}

// Transparent makes the background transparent.
func () Option {
	return func( *Graph) error {
		.Builder.Transparent = true

		return nil
	}
}

// LeftYAxis configures the left Y axis.
func ( ...axis.Option) Option {
	return func( *Graph) error {
		.Builder.GraphPanel.Yaxes[0] = *axis.New(...).Builder

		return nil
	}
}

// RightYAxis configures the right Y axis.
func ( ...axis.Option) Option {
	return func( *Graph) error {
		.Builder.GraphPanel.Yaxes[1] = *axis.New(...).Builder

		return nil
	}
}

// XAxis configures the X axis.
func ( ...axis.Option) Option {
	return func( *Graph) error {
		.Builder.GraphPanel.Xaxis = *axis.New(...).Builder

		return nil
	}
}

// Alert creates an alert for this graph.
func ( string,  ...alert.Option) Option {
	return func( *Graph) error {
		.Alert = alert.New(.Builder.Title, append(, alert.Summary())...)
		.Alert.Builder.Name = .Builder.Title

		return nil
	}
}

// Draw specifies how the graph will be drawn.
func ( ...DrawMode) Option {
	return func( *Graph) error {
		.Builder.Bars = false
		.Builder.Lines = false
		.Builder.Points = false

		for ,  := range  {
			switch  {
			case Bars:
				.Builder.Bars = true
			case Lines:
				.Builder.Lines = true
			case Points:
				.Builder.Points = true
			default:
				return errors.ErrInvalidArgument
			}
		}

		return nil
	}
}

// Fill defines the amount of color fill for a series (default 1, max 10, 0 is none).
func ( int) Option {
	return func( *Graph) error {
		if  < 0 ||  > 10 {
			return fmt.Errorf("fill must be between 0 and 10: %w", errors.ErrInvalidArgument)
		}

		.Builder.Fill = 

		return nil
	}
}

// LineWidth defines the width of the line for a series (default 1, max 10, 0 is none).
func ( uint) Option {
	return func( *Graph) error {
		if  > 10 {
			return fmt.Errorf("line width must be between 0 and 10: %w", errors.ErrInvalidArgument)
		}

		.Builder.Linewidth = 

		return nil
	}
}

// Staircase draws adjacent points as staircase.
func () Option {
	return func( *Graph) error {
		.Builder.GraphPanel.SteppedLine = true

		return nil
	}
}

// PointRadius adjusts the size of points when Points are selected as Draw Mode.
func ( float32) Option {
	return func( *Graph) error {
		if  < 0 ||  > 10 {
			return fmt.Errorf("point radius must be between 0 and 10: %w", errors.ErrInvalidArgument)
		}

		.Builder.Pointradius = 

		return nil
	}
}

// Null configures how null values are displayed.
func ( NullValue) Option {
	return func( *Graph) error {
		.Builder.GraphPanel.NullPointMode = string()

		return nil
	}
}

// Repeat configures repeating a panel for a variable
func ( string) Option {
	return func( *Graph) error {
		.Builder.Repeat = &

		return nil
	}
}

// RepeatDirection configures repeating vertical or horizontal
func ( sdk.RepeatDirection) Option {
	return func( *Graph) error {
		.Builder.RepeatDirection = &

		return nil
	}
}

// SeriesOverride configures how null values are displayed.
// See https://grafana.com/docs/grafana/latest/panels/field-options/
func ( ...series.OverrideOption) Option {
	return func( *Graph) error {
		 := sdk.SeriesOverride{}

		for ,  := range  {
			if  := (&);  != nil {
				return 
			}
		}

		.Builder.GraphPanel.SeriesOverrides = append(.Builder.GraphPanel.SeriesOverrides, )

		return nil
	}
}

// Legend defines what should be shown in the legend.
func ( ...LegendOption) Option {
	return func( *Graph) error {
		 := sdk.Legend{Show: true}

		for ,  := range  {
			switch  {
			case Hide:
				.Show = false
			case AsTable:
				.AlignAsTable = true
			case ToTheRight:
				.RightSide = true
			case Min:
				.Min = true
				.Values = true
			case Max:
				.Max = true
				.Values = true
			case Avg:
				.Avg = true
				.Values = true
			case Current:
				.Current = true
				.Values = true
			case Total:
				.Total = true
				.Values = true
			case NoNullSeries:
				.HideEmpty = true
			case NoZeroSeries:
				.HideZero = true
			default:
				return errors.ErrInvalidArgument
			}
		}

		.Builder.GraphPanel.Legend = 

		return nil
	}
}